
#include "Sample.h"
#include <cstring>
#include <cstdio>
#include <cassert>
#include <cstdlib>

static void __logger(const CALchar *msg)
{
    fprintf(stderr, msg);
}

CALint Initialize(CALdevice *device, CALcontext *ctx, CALuint deviceNum )
{
    //////////////////////////////////////////////////////////////////////////
    //
    //  Initialization phase
    //
    if (calInit() != CAL_RESULT_OK )
    {
        fprintf(stderr, "There was an error initializing CAL.\n");
        fprintf(stderr, "Error string is %s\n", calGetErrorString());
        return 0;
    }

    // Open the first device
    if (calDeviceOpen(device, deviceNum) != CAL_RESULT_OK )
    {
        fprintf(stderr, "There was an error opening the device %d.\n", deviceNum);
        fprintf(stderr, "Error string is %s\n", calGetErrorString());
        return 0;
    }

    // Create a CAL context
    if (calCtxCreate(ctx, *device) != CAL_RESULT_OK )
    {
        fprintf(stderr, "There was an error creatint the context.\n");
        fprintf(stderr, "Error string is %s\n", calGetErrorString());
        return 0;
    }

    return 1;
}

CALint SetupKernel(const CALchar* ILKernel, CALmodule* module, CALcontext* ctx, CALboolean disassemble)
{
    //////////////////////////////////////////////////////////////////////////
    //
    // compile, link and load the kernel program
    //
    CALimage image = NULL;
    CALboolean success = CAL_FALSE;

    // Get device specific information
    CALdeviceattribs attribs;

    attribs.struct_size = sizeof(CALdeviceattribs);
    if (calDeviceGetAttribs(&attribs, 0) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error getting device attribs.\n");
        fprintf(stderr, "Error string is %s\n", calGetErrorString());
        return 0;
    }

    // Compile IL kernel into object
    CALobject obj;
    if (calclCompile(&obj, CAL_LANGUAGE_IL, ILKernel, attribs.target) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error compiling the program.\n");
        fprintf(stderr, "Error string is %s\n", calclGetErrorString());
        return 0;
    }

    // Link object into an image
    if (calclLink(&image, &obj, 1) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error linking the program.\n");
        fprintf(stderr, "Error string is %s\n", calclGetErrorString());
        return 0;
    }

    if (calclFreeObject(obj) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error freeing the compiler object.\n");
        fprintf(stderr, "Error string: %s\n", calclGetErrorString());
        return 0;
    }
    if (disassemble == CAL_TRUE)
    {
        calclDisassembleImage(image, __logger);
    }

    // Load module into the context
    if (calModuleLoad(module, *ctx, image) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error loading the program module.\n");
        fprintf(stderr, "Error string is %s\n", calGetErrorString());
        return 0;
    }

    if (calclFreeImage(image) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error freeing the program image.\n");
        fprintf(stderr, "Error string is %s\n", calGetErrorString());
        return 0;
    }

    return 1;
}

CALint AssembleKernel(const CALchar* ILKernel, CALmodule* module, CALcontext* ctx, CALCLprogramType asmLang, CALboolean Disassemble)
{
    //////////////////////////////////////////////////////////////////////////
    //
    // compile, link and load the kernel program
    //
    CALimage image = NULL;
    CALboolean success = CAL_FALSE;

    // Get device specific information
    CALdeviceattribs attribs;

    attribs.struct_size = sizeof(CALdeviceattribs);
    if (calDeviceGetAttribs(&attribs, 0) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error getting device attribs.\n");
        fprintf(stderr, "Error string is %s\n", calGetErrorString());
        return 0;
    }

#ifdef ATI_OS_LINUX
    fprintf(stderr, "Linux assembly is not currently supported.\n");
    return 0;
#endif
    // Compile IL kernel into object
    CALobject obj;
    if (calclAssembleObject(&obj, asmLang, ILKernel, attribs.target) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error compiling the program.\n");
        fprintf(stderr, "Error string is %s\n", calclGetErrorString());
        return 0;
    }

    // Link object into an image
    if (calclLink(&image, &obj, 1) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error linking the program.\n");
        fprintf(stderr, "Error string is %s\n", calclGetErrorString());
        return 0;
    }

    if (calclFreeObject(obj) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error freeing the compiler object.\n");
        fprintf(stderr, "Error string: %s\n", calclGetErrorString());
        return 0;
    }
    if (Disassemble == CAL_TRUE)
    {
        calclDisassembleImage(image, __logger);
    }

    // Load module into the context
    if (calModuleLoad(module, *ctx, image) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error loading the program module.\n");
        fprintf(stderr, "Error string is %s\n", calGetErrorString());
        return 0;
    }

    if (calclFreeImage(image) != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error freeing the program image.\n");
        fprintf(stderr, "Error string is %s\n", calGetErrorString());
        return 0;
    }

    return 1;
}

CALint RunProgram(CALcontext *ctx, CALmodule *module, CALuint Width, CALuint Height, SampleInfo* Info)
{
    //////////////////////////////////////////////////////////////////////////
    //
    //  get the program entry point and run the program
    //
    CALfunc func;
    CALresult r = CAL_RESULT_ERROR;
    if (calModuleGetEntry(&func, *ctx, *module, "main") != CAL_RESULT_OK)
    {
        fprintf(stderr, "There was an error finding the program entry point.\n");
        fprintf(stderr, "Error string is %s\n", calGetErrorString());
        return 0;
    }

    // Setup a computation domain
    CALdomain rect;
    rect.x = 0;
    rect.y = 0;
    rect.width = Width/2;
    rect.height = Height/2;


    // Execute the program iterations number of times
    CALevent event = 0;
    Info->Kernel.Start();
    r = calCtxRunProgram(&event, *ctx, func, &rect);
    if (r != CAL_RESULT_OK)
    {
	fprintf(stderr, "There was an error running the program, Error code: %d.\n", r);
	fprintf(stderr, "Error string is %s\n", calGetErrorString());
	return 0;
    }

    // Wait for the last run to complete.
    while(calCtxIsEventDone(*ctx, event) == CAL_RESULT_PENDING);
    Info->Kernel.Stop();

    return 1;
}

CALint Cleanup(CALdevice* device, CALcontext* ctx, CALmodule* module, CALresource* &resourceHandler, CALmem* &memoryHandler, Data* &data, CALuint numHandles, CALboolean waitForUser )
{
    if (data)
    {
        for (CALuint i = 0; i < numHandles; ++i)
        {
            if (data[i].c_data)
            {
                delete [] data[i].c_data;
            }
        }
    }
    if (memoryHandler)
    {
        // Release the memory with the handles
        for (CALuint i = 0; i < numHandles; i++ )
        {
            if (memoryHandler[i] )
            {
                if (calCtxReleaseMem(*ctx, memoryHandler[i]) != CAL_RESULT_OK )
                {
                    fprintf(stderr, "There was an error releasing memory handle %d.\n", i);
                    fprintf(stderr, "Error string is %s\n", calGetErrorString());
                }
            }
        }
    }

    // Free up the CALresource
    if (resourceHandler)
    {
        for (CALuint i = 0; i < numHandles; i++ )
        {
            if (resourceHandler[i] )
            {
                if (calResFree(resourceHandler[i]) != CAL_RESULT_OK )
                {
                    fprintf(stderr, "There was an error releasing resource handle %d.\n", i);
                    fprintf(stderr, "Error string is %s\n", calGetErrorString());
                }
            }
        }
    }

    // Unload the module from the context
    if (module)
    {
        if (calModuleUnload(*ctx, *module) != CAL_RESULT_OK )
        {
            fprintf(stderr, "Error string is %s\n", calGetErrorString());
        }
    }

    // Destroy the context
    if (ctx )
    {
        if (calCtxDestroy(*ctx) != CAL_RESULT_OK )
        {
            fprintf(stderr, "Error string is %s\n", calGetErrorString());
        }
    }

    // Close the device
    if (device )
    {
        if (calDeviceClose(*device) != CAL_RESULT_OK )
        {
            fprintf(stderr, "There was an error closing the device.\n");
            fprintf(stderr, "Error string is %s\n", calGetErrorString());
        }
    }

    // Shutdown cal device
    if (calShutdown() != CAL_RESULT_OK )
    {
        fprintf(stderr, "There was an error during cal shutdown.\n");
        fprintf(stderr, "Error string is %s\n", calGetErrorString());
    }

    delete[] resourceHandler;
    delete[] memoryHandler;
    delete[] data;

    if (waitForUser == CAL_TRUE )
    {
        printf("\nPress enter to exit...\n");
        getchar();
    }

    return 1;
}

    CALformat
getFormat(CALuint formatSize, CALuint dataSize)
{
    CALformat format = CAL_FORMAT_FLOAT_1;

    switch(dataSize)
    {
        case 4:

            switch(formatSize)
            {
                case 1:
                    format = CAL_FORMAT_FLOAT_1;
                    break;
                case 2:
                    format = CAL_FORMAT_FLOAT_2;
                    break;
                case 4:
                    format = CAL_FORMAT_FLOAT_4;
                    break;
                default:
                    assert(!"attempted to use invalid format!" );
                    break;
            };
            break;
        case 8:
            switch(formatSize)
            {
                case 1:
                    format = CAL_FORMAT_FLOAT_2;
                    break;
                case 2:
                    format = CAL_FORMAT_FLOAT_4;
                    break;
                default:
                    assert(!"attempted to use invalid format!" );
                    break;
            };

            break;
        case 16:
            switch(formatSize)
            {
                case 1:
                    format = CAL_FORMAT_FLOAT_4;
                    break;
                default:
                    assert(!"attempted to use invalid format!" );
                    break;
            };
            break;
    }

    return format;
}

CALboolean ParseCommandLine(CALuint argc, CALchar* argv[], SampleInfo* Info)
{
    Info->Verbose = CAL_FALSE;
    Info->Verify = CAL_FALSE;
    Info->PrintIL = CAL_FALSE;
    Info->Disassemble = CAL_FALSE;
    Info->Quiet = CAL_FALSE;
    Info->Timing = CAL_FALSE;
    Info->SrcCacheable = CAL_FALSE;
    Info->DstCacheable = CAL_TRUE;
    Info->DeviceNum = 0;
    Info->Width = 512;
    Info->Height = 512;
    Info->Iterations = 9;
    Info->SrcComponents = 4;
    Info->SrcMemory = 'g';
    Info->DstComponents = 4;
    Info->DstMemory = 'c';
    Info->System.Reset();
    Info->Kernel.Reset();

	printf("Use -? for help\n");

    for (CALuint x = 1; x < argc; ++x)
    {
        switch(argv[x][1])
        {
            default:
				Usage(argv[0]);
                return CAL_FALSE;
            case 'q':
                Info->Quiet = CAL_TRUE;
                break;
            case '?':
                Usage(argv[0]);
                return CAL_FALSE;
            case 'e':
                Info->Verify = CAL_TRUE;
                break;
            case 'v':
                Info->Verbose = CAL_TRUE;
                break;
            case 'p':
                Info->PrintIL = CAL_TRUE;
                break;
            case 'a':
                Info->Disassemble = CAL_TRUE;
                break;
            case 't':
                Info->Timing = CAL_TRUE;
                break;
            case 'i':
                switch(argv[x][2])
                {
                    case 'm':
                        Info->SrcCacheable = CAL_TRUE;
                        break;
                    case 'l':
                        if (++x < argc)
                        {
                            Info->SrcMemory = argv[x][0];
                            if (Info->SrcMemory != 'c' && Info->SrcMemory != 'g')
                            {
                                fprintf(stderr, "Invalid source memory type\n" );
                                return CAL_FALSE;
                            }
                        }
                        else
                        {
                            return CAL_FALSE;
                        }
                        break;
                    case 'c':
                        if (++x < argc)
                        {
                            sscanf(argv[x], "%u", &Info->SrcComponents);
                            if (Info->SrcComponents > 4 || Info->SrcComponents == 3)
                            {
                                fprintf(stderr, "Only source component sizes of 1, 2 and 4 accepted\n" );
                                return CAL_FALSE;
                            }
                        }
                        else
                        {
                            return CAL_FALSE;
                        }
                        break;
                };
                break;
            case 'o':
                switch(argv[x][2])
                {
                    case 'm':
                        Info->DstCacheable = CAL_TRUE;
                        break;
                    case 'l':
                        if (++x < argc)
                        {
                            Info->DstMemory = argv[x][0];
                            if (Info->DstMemory != 'c' && Info->DstMemory != 'g')
                            {
                                fprintf(stderr, "Invalid destination memory type\n" );
                                return CAL_FALSE;
                            }
                        }
                        else
                        {
                            return CAL_FALSE;
                        }
                        break;
                    case 'c':
                        if (++x < argc)
                        {
                            sscanf(argv[x], "%u", &Info->DstComponents);
                            if (Info->DstComponents > 4 || Info->DstComponents == 3)
                            {
                                fprintf(stderr, "Only destination component sizes of 1, 2 and 4 accepted\n" );
                                return CAL_FALSE;
                            }
                        }
                        else
                        {
                            return CAL_FALSE;
                        }
                        break;
                };
                break;
            case 'w':
                if (++x < argc)
                {
                    sscanf(argv[x], "%u", &Info->Width);
                }
                else
                {
                    return CAL_FALSE;
                }
                break;
            case 'h':
                if (++x < argc)
                {
                    sscanf(argv[x], "%u", &Info->Height);
                }
                else
                {
                    return CAL_FALSE;
                }
                break;
            case 'r':
                if (++x < argc)
                {
                    sscanf(argv[x], "%u", &Info->Iterations);
                }
                else
                {
                    return CAL_FALSE;
                }
                break;
            case 'd':
                if (++x < argc)
                {
                    sscanf(argv[x], "%u", &Info->DeviceNum);
                }
                else
                {
                    return CAL_FALSE;
                }
                break;
        };
    }
    return CAL_TRUE;
}

CALvoid Usage(const CALchar* name)
{
    fprintf(stderr,"Usage: %s [-h|-e|-v|-p|-t|-a]"
            " [-oc <1|2|4>] [-ic <1|2|4>] [-ol <c|g>] [-il <c|g>]"
            " [-w <integer>] [-h <integer>] [-d <deviceNum>]"
            " [-r <Iteration Count>]\n\n", name);
    fprintf(stderr, "\t-?        Display this help information\n" );
    fprintf(stderr, "\t-e        Verify Computational Correctness\n" );
    fprintf(stderr, "\t-v        Display Verbose Output\n" );
    fprintf(stderr, "\t-q        Supress Display Output\n" );
    fprintf(stderr, "\t-p        Print the program kernel\n" );
    fprintf(stderr, "\t-a        Print the disassembled kernel image\n" );
    fprintf(stderr, "\t-t        Print out the timing information\n" );
    fprintf(stderr, "\t-im       Flag to use cacheable input memory*, default non-cacheable\n" );
    fprintf(stderr, "\t-om       Flag to use cacheable output memory*, default non-cacheable\n" );
    fprintf(stderr, "\t-oc <N>   Number of output Components, where N = {1|2|4}, default N = 4\n" );
    fprintf(stderr, "\t-ic <N>   Number of input Components, where N = {1|2|4}, default N = 4\n" );
    fprintf(stderr, "\t-ol <c|g> Specify the output location, c = CPU, g = GPU, default GPU\n" );
    fprintf(stderr, "\t-il <c|g> Specify the input location, c = CPU, g = GPU, default GPU\n" );
    fprintf(stderr, "\t-w  <int> Flag to specify number of threads wide the data set is, default 256\n" );
    fprintf(stderr, "\t-h  <int> Flag to specify number of threads high the data set is, default 256\n" );
    fprintf(stderr, "\t-r  <int> Number of iterations to run the program\n" );
    fprintf(stderr, "\t-d  <int> Device number to connect to, default 0\n" );
    fprintf(stderr, "*The cacheable memory flags may cause failures if the amount\n"
            " of cacheable memory is smaller than the requested memory\n"
            " size. Cacheable memory is machine dependent, so use with\n"
            " caution.\n");
}

void PrintKernel(const CALchar* string, const CALchar* name)
{
    fprintf(stderr, "Kernel: %s\n", name);
    fprintf(stderr, "%s\n", string);
}

void PrintPerfHeader(const CALchar* progName)
{
    printf("Program: %30s\t\tKernel\tSystem\n", progName);
    printf(" WxH\t\tIn-Out\t Src\t Dst\t Iter\tGB/sec\tGB/sec\n" );
}

void PrintPerfStats(CALuint nInput, CALuint nOutput, SampleInfo& Info, CALuint Bytes)
{
    //////////////////////////////////////////////////////////////////////////
    //
    //  compute statistics
    //
    CALdouble giga = 1024.0 * 1024.0 * 1024.0;
    CALdouble single = (CALdouble)(Info.Width * Info.Height * Bytes) / giga;
    CALdouble srcbytes = (CALdouble)(Info.SrcComponents * Info.Iterations) * single *(CALdouble)nInput;
    CALdouble dstbytes = (CALdouble)(Info.DstComponents * Info.Iterations) * single *(CALdouble)nOutput;
    CALdouble kernelgbsec = (srcbytes + dstbytes) / Info.Kernel.GetElapsedTime();
    CALdouble systemgbsec = Info.Iterations*(srcbytes + dstbytes) / Info.System.GetElapsedTime();


    printf("%4dx%4d\t%d  %d\t %d\t %d\t %d\t%3.2f\t%3.2f\n",
            Info.Width, Info.Height, nInput , nOutput, Info.SrcComponents, Info.DstComponents, Info.Iterations, kernelgbsec, systemgbsec);

}

static void copyFrom(CALchar* ptr, Data& data, CALuint pitch)
{
    // Fast path that just does a memcpy of the whole data set
    if (pitch == data.Width)
    {
        memcpy(data.c_data, ptr, data.DataSize * data.ComponentSize * data.Width * data.Height);
    }
    else
    {
        CALuint d_idx = 0;
        CALuint s_idx = 0;
        CALuint elem_size = data.ComponentSize * data.DataSize;
        for (CALuint y = 0; y < data.Height; ++y)
        {
            for (CALuint x = 0; x <(data.Width * data.ComponentSize * data.DataSize); ++x)
            {
                data.c_data[d_idx + x] = ptr[s_idx + x];
                //memcpy(data.c_data + d_idx, ptr + s_idx, elem_size * data.Width);
            }
            d_idx += (data.Width * elem_size);
            s_idx += (pitch * elem_size);
        }
    }
}

CALint CopyDataFromGPU(CALcontext* ctx, CALresource* _Res, Data* data, CALuint num)
{
    CALuint pitch;
    CALresult r;
    CALchar* ptr;
    for (CALuint i = 0; i < num; ++i)
    {
        r = calResMap((CALvoid**)&ptr, &pitch, _Res[i], 0);
        if (r != CAL_RESULT_OK)
        {
            fprintf(stderr, "%s:%d - An error occured: %d\n",__FILE__, __LINE__, r);
            fprintf(stderr, "Error string is %s\n",calGetErrorString());
            return 0;
        }
        copyFrom(ptr, data[i],(data[i].Linear == CAL_TRUE) ? data[i].Width : pitch);
        r = calResUnmap(_Res[i]);
        if (r != CAL_RESULT_OK)
        {
            fprintf(stderr, "%s:%d - An error occured: %d\n",__FILE__, __LINE__, r);
            fprintf(stderr, "Error string is %s\n",calGetErrorString());
            return 0;
        }
    }
    return 1;
}

static void copyTo(CALchar* ptr, Data& data, CALuint pitch)
{
    // Fast path that just does a memcpy of the whole data set
    if (pitch == data.Width)
    {
        memcpy(ptr, data.c_data, data.DataSize * data.ComponentSize * data.Width * data.Height);
    }
    else
    {
        CALuint d_idx = 0;
        CALuint s_idx = 0;
        CALuint elem_size = data.ComponentSize * data.DataSize;
        for (CALuint y = 0; y < data.Height; ++y)
        {
            for (CALuint x = 0; x <(data.Width * data.ComponentSize * data.DataSize); ++x)
            {
                ptr[d_idx + x] = data.c_data[s_idx + x];
                //memcpy(ptr + d_idx, data.c_data + s_idx, elem_size * data.Width);
            }
            s_idx += (data.Width * elem_size);
            d_idx += (pitch * elem_size);
        }
    }
}


CALint CopyDataToGPU(CALcontext* ctx, CALresource* _Res, Data* data, CALuint num)
{
    CALuint pitch;
    CALresult r;
    CALchar* ptr;
    for (CALuint i = 0; i < num; ++i)
    {
        r = calResMap((CALvoid**)&ptr, &pitch, _Res[i], 0);
        if (r != CAL_RESULT_OK)
        {
            fprintf(stderr, "%s:%d - An error occured: %d\n",__FILE__, __LINE__, r);
            fprintf(stderr, "Error string is %s\n",calGetErrorString());
            return 0;
        }
        copyTo(ptr, data[i],(data[i].Linear == CAL_TRUE) ? data[i].Width: pitch);
        r = calResUnmap(_Res[i]);
        if (r != CAL_RESULT_OK)
        {
            fprintf(stderr, "%s:%d - An error occured: %d\n",__FILE__, __LINE__, r);
            fprintf(stderr, "Error string is %s\n",calGetErrorString());
            return 0;
        }
    }
    return 1;
}

CALint BindIONames(CALcontext* ctx, CALmodule* module, CALmem* _Mem, 
        CALuint iStop, CALuint cStop, CALuint oStop, Data* data)
{
    CALname progName = 0;
    CALresult r = CAL_RESULT_ERROR;
    for (CALuint i = 0; i < oStop; ++i)
    {
        CALchar buffer[10];
        if (i < iStop)
        {
            if (data[i].Linear == CAL_TRUE)
            {
                if (data[i].Scratch == CAL_TRUE)
                {
                    sprintf(buffer, "x[]");
                }
                else
                {
                    sprintf(buffer, "g[]");
                }
            }
            else
            {
                sprintf(buffer,"i%d", i);
            }
        }
        else if (i >= cStop && i < oStop)
        {
            if (data[i].Linear == CAL_TRUE)
            {
                sprintf(buffer, "g[]");
            }
            else
            {
                sprintf(buffer,"o%d", i - cStop);
            }
        }
        else if (i >= iStop && i < cStop)
        {
            sprintf(buffer,"cb%d", i - iStop);
        }
        else
        {
            fprintf(stderr, "Error: Path that should be unreachable is reached\n");
            return 0;
        }
        r = calModuleGetName(&progName, *ctx, *module, buffer);
        if (r != CAL_RESULT_OK)
        {
            fprintf(stderr, "%s:%d - An error occured: %d\n",__FILE__, __LINE__, r);
            fprintf(stderr, "Error string is %s\n",calGetErrorString());
            fprintf(stderr, "Failing name binding was %s\n", buffer);
            return 0;
        }
        r = calCtxSetMem(*ctx, progName, _Mem[i]);
        if (r != CAL_RESULT_OK)
        {
            fprintf(stderr, "%s:%d - An error occured: %d\n",__FILE__, __LINE__, r);
            fprintf(stderr, "Error string is %s\n",calGetErrorString());
            return 0;
        }
    }
    return 1;
}

CALint AllocateResources(CALcontext* ctx, CALdevice* device, CALresource* &_Res, CALmem* &_Mem, CALuint iStop, CALuint cStop, CALuint oStop, Data* data, const SampleInfo& Info)
{
    CALresult r = CAL_RESULT_ERROR;
    _Res = new CALresource[oStop];
    _Mem = new CALmem[oStop];
    //////////////////////////////////////////////////////////////////////////
    //
    //  allocate input and output resources and map them into the context
    //
    for (CALuint i = 0; i < oStop; ++i)
    {
        CALint tWidth = data[i].Width;;
        CALint tHeight = data[i].Height;
        CALresallocflags flag = (data[i].Linear == CAL_TRUE) ? CAL_RESALLOC_GLOBAL_BUFFER :(CALresallocflags)0;
        CALint mComponents = data[i].ComponentSize;
        CALchar mem = 'g';
        if (i < iStop)
        {
            mem = Info.SrcMemory;
            flag = static_cast<CALresallocflags>(flag |((Info.SrcCacheable == CAL_TRUE) ? CAL_RESALLOC_CACHEABLE : flag));
        }
        else if (i >= cStop && i < oStop)
        {
            mem = Info.DstMemory;
            flag = static_cast<CALresallocflags>(flag |((Info.DstCacheable == CAL_TRUE) ? CAL_RESALLOC_CACHEABLE : flag));
        }
        else if (i >= iStop && i < cStop)
        {
            continue;
        }
        else
        {
            fprintf(stderr, "Error: Path that should be unreachable is reached\n");
            return 0;
        }
        switch(mem)
        {
            case 'g':
                r = calResAllocLocal2D(&_Res[i], *device, tWidth, tHeight, getFormat(mComponents, data[i].DataSize), flag);
                break;
            case 'c':
                r = calResAllocRemote2D(&_Res[i], device, 1, tWidth, tHeight, getFormat(mComponents, data[i].DataSize), flag);
                break;
        }
        if (r != CAL_RESULT_OK)
        {
            fprintf(stderr, "%s:%d - An error occured: %d\n",__FILE__, __LINE__, r);
            fprintf(stderr, "Error string is %s\n",calGetErrorString());
            return 0;
        }
        r = calCtxGetMem(&_Mem[i], *ctx, _Res[i]);
        if (r != CAL_RESULT_OK)
        {
            fprintf(stderr, "%s:%d - An error occured: %d\n",__FILE__, __LINE__, r);
            fprintf(stderr, "Error string is %s\n",calGetErrorString());
            return 0;
        }
    }

    /* Setup constant resources/memory handles */
    for (CALuint i = iStop; i < cStop; ++i)
    {
        CALint cWidth = data[i].Width * data[i].Height;
        r = calResAllocRemote1D(&_Res[i], device, 1, cWidth, getFormat(data[i].ComponentSize,data[i].DataSize), 0);
        if (r != CAL_RESULT_OK)
        {
            fprintf(stderr, "%s:%d - An error occured: %d\n",__FILE__, __LINE__, r);
            fprintf(stderr, "Error string is %s\n",calGetErrorString());
            return 0;
        }
        r = calCtxGetMem(&_Mem[i], *ctx, _Res[i]);
        if (r != CAL_RESULT_OK)
        {
            fprintf(stderr, "%s:%d - An error occured: %d\n",__FILE__, __LINE__, r);
            fprintf(stderr, "Error string is %s\n",calGetErrorString());
            return 0;
        }
    }
    return 1;
}

CALvoid AllocateMemory(Data& data, CALuint tWidth, CALuint tHeight, CALuint CompSize, CALuint DataSize, CALresallocflags flags)
{
    data.Linear = ((flags & CAL_RESALLOC_GLOBAL_BUFFER) == CAL_RESALLOC_GLOBAL_BUFFER) ? CAL_TRUE : CAL_FALSE;
    data.DataSize = DataSize;
    data.Width = tWidth;
    data.Height = tHeight;
    if (CompSize != 1 && CompSize != 2 && CompSize != 4)
    {
        fprintf(stderr,"AllocateMemory Error: Component size must be either 1, 2 or 4\n");
        fprintf(stderr, "Setting CompSize to 4\n");
        CompSize = 4;
    }
    data.ComponentSize = CompSize;
    data.c_data = new CALchar[tWidth * DataSize * CompSize * tHeight];
    memset((void*)data.c_data, 0, tWidth * DataSize * CompSize * tHeight);
}

CALint ParameterValidation(CALuint nInput, CALuint nOutput, SampleInfo* Info, CALdeviceattribs* attribs)
{
    CALint retval = 1;
    CALuint mult = 0;
    CALuint mega = 1024 * 1024;
    CALuint pitch = (Info->Width + 63) &(~63);
    CALuint single = (pitch * Info->Height * sizeof(CALfloat));
    CALuint srcbytes = Info->SrcComponents *(CALuint)single * nInput / mega;
    CALuint dstbytes = Info->DstComponents *(CALuint)single * nOutput / mega;
    mult += (Info->SrcCacheable == CAL_TRUE) ? 1 : 0;
    mult += (Info->DstCacheable == CAL_TRUE) ? 1 : 0;
    if (Info->SrcMemory == 'c')
    {
        if (Info->SrcCacheable == CAL_TRUE &&(mult * srcbytes) >= attribs->cachedRemoteRAM)
        {
            retval = 0;
        }
        else if (srcbytes >= attribs->uncachedRemoteRAM)
        {
            retval = 0;
        }
    }
    else
    {
        if (Info->SrcCacheable == CAL_TRUE &&(mult * srcbytes) >= attribs->cachedRemoteRAM)
        {
            retval = 0;
        }
        else if (srcbytes >= attribs->uncachedRemoteRAM)
        {
            retval = 0;
        }
        else if (srcbytes >= attribs->localRAM)
        {
            retval = 0;
        }
    }
    if (Info->DstMemory == 'c')
    {
        if (Info->DstCacheable == CAL_TRUE &&(mult * dstbytes) >= attribs->cachedRemoteRAM)
        {
            retval = 0;
        }
        else if (dstbytes >= attribs->uncachedRemoteRAM)
        {
            retval = 0;
        }
    }
    else
    {
        if (Info->DstCacheable == CAL_TRUE &&(mult * dstbytes) >= attribs->cachedRemoteRAM)
        {
            retval = 0;
        }
        else if (dstbytes >= attribs->uncachedRemoteRAM)
        {
            retval = 0;
        }
        else if (dstbytes >= attribs->localRAM)
        {
            retval = 0;
        }
    }
    return retval;
}


CALvoid SupportedCALVersion(CALVersion *calVersion)
{
	calVersion->major = 1;
	calVersion->minor = 3;
	calVersion->imp = 185;
	printf("Supported CAL Runtime Version: %d.%d.%d\n", 
			calVersion->major, calVersion->minor, calVersion->imp);
}
CALint QueryDeviceCaps(CALuint DeviceNum, SampleFeatures *FeatureList)
{
	CALboolean capable = CAL_TRUE;

	// Get device attributes
	CALdeviceattribs attribs;
    attribs.struct_size = sizeof(CALdeviceattribs);
    if (calDeviceGetAttribs(&attribs, DeviceNum) != CAL_RESULT_OK)
    {
		fprintf(stderr, "Could not get device attributes.\n");
		capable = CAL_FALSE;
        return capable;
    }
	
	// Check for requested features
	if(FeatureList->DoublePrecision == CAL_TRUE)	{
		if(!attribs.doublePrecision)		{
			capable = CAL_FALSE;
		}
	}
	if(FeatureList->ComputeShaders == CAL_TRUE)	{
		if(!attribs.computeShader)		{
			capable = CAL_FALSE;
		}
	}
	if(FeatureList->LocalDataShares == CAL_TRUE)	{
		if(!attribs.localDataShare)		{
			capable = CAL_FALSE;
		}
	}
	if(FeatureList->GlobalDataShares == CAL_TRUE)	{
		if(!attribs.globalDataShare)	{
			capable = CAL_FALSE;
		}
	}
	if(FeatureList->MemExport == CAL_TRUE)	{
		if(!attribs.memExport)	{
			capable = CAL_FALSE;
		}
	}

	return capable;
}

CALint QueryCALVersion(CALVersion required, const CALchar* comparison)
{
	CALVersion available;
	calGetVersion(&available.major, &available.minor, &available.imp);
	printf("Found CAL Runtime Version: %d.%d.%d\n", available.major, available.minor, available.imp);

	if( strcmp(comparison,">") == 0 )
	{
		if( (available.major > required.major) ||
			(available.major == required.major && available.minor > required.minor) ||
			(available.major == required.major && available.minor == required.minor && 
			 available.imp > required.imp))
		{
			return 1;
		}
	}
	else if( strcmp(comparison,">=") == 0 )
	{
		if( (available.major > required.major) ||
			(available.major == required.major && available.minor > required.minor) ||
			(available.major == required.major && available.minor == required.minor && 
			 available.imp >= required.imp))
		{
			return 1;
		}
	}
	else if( strcmp(comparison,"<") == 0 )
	{
		if( (available.major < required.major) ||
			(available.major == required.major && available.minor < required.minor) ||
			(available.major == required.major && available.minor == required.minor && 
			 available.imp < required.imp))
		{
			return 1;
		}
	}
	else if( strcmp(comparison,"<=") == 0 )
	{
		if( (available.major < required.major) ||
			(available.major == required.major && available.minor < required.minor) ||
			(available.major == required.major && available.minor == required.minor && 
			 available.imp <= required.imp))
		{
			return 1;
		}
	}
	else if( strcmp(comparison,"==") == 0 )
	{
		if( available.major == required.major && available.minor == required.minor &&
			 available.imp == required.imp )
		{
			return 1;
		}
	}
	else 
	{
		fprintf(stderr, "Error. Invalid comparison operator: %s (QueryCALVersion)\n", comparison);
	}

	return 0;
}

CALint ValidateCALRuntime()
{
	CALVersion supportedCALRuntime;

	// Get the CAL runtime currently supported by the SDK 
	SupportedCALVersion( &supportedCALRuntime );

	// Check if this runtime is available 
	return QueryCALVersion( supportedCALRuntime, ">=" );
}
